<?php
// +----------------------------------------------------------------------
// | 在我们年轻的城市里，没有不可能的事！
// +----------------------------------------------------------------------
// | Copyright (c) 2020 http://utils All rights reserved.
// +----------------------------------------------------------------------
// | Author : Jansen <6206574@qq.com>
// +----------------------------------------------------------------------
/**
 * 腾讯云TMT机器翻译驱动
 * 支持传入的config参数如下：
 *   secretId：string，必填，密钥ID
 *   secretKey：string，必填，密钥
 *   nearby：true|false，可选，是否启用就近接入
 *   region：string，可选，地区标识
 *   projectId：integer，可选，项目ID
 * @package jansen\utils\translate\drivers
 */
namespace jansen\utils\translate\drivers;
use jansen\utils\translate\exception\TencentTranslateException;
class Tencent implements TranslateInterface{
    /**
     * @var bool $nearby 是否启用就近接入，region设置为金融区域时无效
     */
    private $nearby = true;
    /**
     * @var string $service API接口的类型，固定为tmt(Tencent Machine Translation 腾讯机器翻译)
     */
    private $service = 'tmt';
    /**
     * @var string $host API接口的主域名
     */
    private $host = 'tencentcloudapi.com';
    /**
     * @var string $action 操作的接口名称。本接口取值：TextTranslate
     */
    private $action = 'TextTranslate';
    /**
     * @var string $region 地域参数，用来标识希望操作哪个地域的数据。
     */
    private $region = 'ap-shanghai';
    /**
     * @var string $version 操作的 API 的版本。本接口取值：2018-03-21
     */
    private $version = '2018-03-21';
    /**
     * @var int 项目ID，可以根据控制台-账号中心-项目管理中的配置填写，如无配置请填写默认项目ID:0
     */
    private $projectId = 0;
    /**
     * @var string $algorithm 签名算法
     */
    private $algorithm = 'TC3-HMAC-SHA256';
    /**
     * @var string $contentType 请求类型及字符集
     */
    private $contentType = 'application/json; charset=utf-8';
    /**
     * @var string $endpoint API接口调用完整域名，由service + region + host组成
     */
    private $endpoint;
    /**
     * @var int $timestamp 当前 UNIX 时间戳，可记录发起 API 请求的时间。注意：如果与服务器时间相差超过5分钟，会引起签名过期错误。
     */
    private $timestamp;
    /**
     * @var string $secretId 密钥ID
     */
    private $secretId;
    /**
     * @var string $secretKey 密钥
     */
    private $secretKey;
    public function __construct(array $config){
        $this->setTimestamp();
        key_exists('nearby', $config) && $this->setNearby($config['nearby']);
        key_exists('region', $config) && $this->setRegion($config['region']);
        key_exists('projectId', $config) && $this->setProjectId($config['projectId']);
        key_exists('secretId', $config) && $this->setSecretId($config['secretId']);
        key_exists('secretKey', $config) && $this->setSecretKey($config['secretKey']);
        $this->setEndpoint();
    }
    /**
     * 启用/禁用 就近接入
     * @param bool $nearby
     * @author:Jansen <6206574@qq.com>
     */
    public function setNearby(bool $nearby){
        $this->nearby = $nearby;
    }
    /**
     * 设置时间戳
     * @return void
     * @author:Jansen <6206574@qq.com>
     */
    public function setTimestamp(){
        $this->timestamp = time();
    }
    /**
     * 设置调用域名
     * @return void
     * @author:Jansen <6206574@qq.com>
     */
    public function setEndpoint(){
        //判断当前配置的区域是否为金融区域，如是，则强制接口调用域名与区域相同
        if (strpos($this->region, '-fsi') !== false){
            $this->setNearby(false);
        }
        $this->endpoint = $this->service.'.'.($this->nearby?'':($this->region.'.')).$this->host;
    }
    /**
     * 设置区域
     * @param string $region 区域名称
     * @return void
     * @throws \Exception
     * @author:Jansen <6206574@qq.com>
     */
    public function setRegion(string $region){
        $this->region = $region;
    }
    /**
     * 设置项目ID
     * @param int $id
     * @return void
     * @author:Jansen <6206574@qq.com>
     */
    public function setProjectId(int $id){
        $this->projectId = $id;
    }
    /**
     * 设置SecretId
     * @param string $secretId
     * @return void
     * @author:Jansen <6206574@qq.com>
     */
    public function setSecretId(string $secretId){
        $this->secretId = $secretId;
    }
    /**
     * 设置SecretKey
     * @param string $secretKey
     * @return void
     * @author:Jansen <6206574@qq.com>
     */
    public function setSecretKey(string $secretKey){
        $this->secretKey = $secretKey;
    }
    /**
     * 文本翻译
     * @param string $content   待翻译文本
     * @param string $target    目标语言
     * @param string $source    源语言
     * @return string
     * @author:Jansen <6206574@qq.com>
     */
    public function text(string $content, string $target='en', string $source='auto'){
        $query['SourceText'] = $content;
        $query['Source'] = $source;
        $query['Target'] = $target;
        $query['ProjectId'] = $this->projectId;
        $httpClient = new \GuzzleHttp\Client();
        $response = $httpClient->post('https://'.$this->endpoint, [
            'connect_timeout'   => 5,
            'timeout'           => 5,
            'headers'           => [
                'Content-Type'  => $this->contentType,
                'X-TC-Action'   => $this->action,
                'X-TC-Region'   => $this->region,
                'X-TC-Timestamp'=> $this->timestamp,
                'X-TC-Version'  => $this->version,
                'Authorization' => $this->authorization(json_encode($query))
            ],
            'json'              => $query
        ]);
        $result = json_decode($response->getBody()->getContents(), true);
        if (isset($result['Response']['Error'])){
            throw new TencentTranslateException($result['Response']['Error']['Message'], $result['Response']['Error']['Code']);
        }
        return $result['Response']['TargetText'];
    }
    /**
     * 计算签名
     * @param string $payload 请求的body内容
     * @return string
     * @author:Jansen <6206574@qq.com>
     */
    private function authorization(string $payload){
        $credentialDate = gmdate('Y-m-d', $this->timestamp);
        $scopeEnd = 'tc3_request';
        //1. 拼接规范请求串
        $canonicalRequest['method']         = 'POST';
        $canonicalRequest['uri']            = '/';
        $canonicalRequest['queryString']    = '';
        //定义参与签名计算的headers信息
        $headers['Content-Type']    = $this->contentType;
        $headers['Host']            = $this->endpoint;
        //-- 将数组的key和value转小写，并按key做升序排序
        $headers = array_map('strtolower', array_change_key_case($headers, CASE_LOWER));
        ksort($headers, SORT_STRING);
        $canonicalHeaders = '';
        foreach($headers as $key => $value){
            $canonicalHeaders .= $key.':'.$value."\n";
        }
        $canonicalRequest['canonicalHeaders']       = $canonicalHeaders;
        $canonicalRequest['signedHeaders']          = implode(';', array_keys($headers));
        $canonicalRequest['hashedRequestPayload']   = hash('SHA256', $payload);
        $unHashCanonicalRequest = implode("\n", $canonicalRequest);
        //2. 拼接待签名字符串
        $stringToSign['algorithm']              = $this->algorithm;
        $stringToSign['requestTimestamp']       = $this->timestamp;
        $stringToSign['credentialScope']        = $credentialDate.'/'.$this->service.'/'.$scopeEnd;
        $stringToSign['HashedCanonicalRequest'] = hash('SHA256', $unHashCanonicalRequest);
        //3. 计算签名
        $secretDate     = hash_hmac('SHA256', $credentialDate, 'TC3'.$this->secretKey, true);
        $secretService  = hash_hmac('SHA256', $this->service, $secretDate, true);
        $secretSigning  = hash_hmac('SHA256', $scopeEnd, $secretService, true);
        $signature      = hash_hmac('SHA256', implode("\n", $stringToSign), $secretSigning);
        //4. 拼接 Authorization
        $authorization  = $this->algorithm.' ';
        $authorization .= 'Credential='.$this->secretId.'/'.$stringToSign['credentialScope'].', ';
        $authorization .= 'SignedHeaders='.$canonicalRequest['signedHeaders'].', ';
        $authorization .= 'Signature='.$signature;
        return $authorization;
    }
}